package com.util;

import com.model.User;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import sun.reflect.ReflectionFactory;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author: hdu_huang
 * @since: 2020/9/21 17:03
 */
@Slf4j
public final class BeanUtils {

    private BeanUtils() {
    }

    private static final ConcurrentHashMap<String, Constructor<?>> cacheConstructor = new ConcurrentHashMap<>(64);
    private static Constructor<Object> objectConstructor;

    static {
        try {
            objectConstructor = Object.class.getDeclaredConstructor();
            objectConstructor.setAccessible(true);
        } catch (Exception e) {
            log.error("BeanUtils init error", e);
        }
    }


    public static void main(String[] args) throws Exception{

        User user = User.builder().name("义刚").age(24).id(1001).build();
        BeanUtils beanUtils = newInstanceUseSerialization(BeanUtils.class);

        Constructor<User> declaredConstructor = User.class.getDeclaredConstructor(null);
        Object name = getDeclaredFieldValue(User.class, "name", user);


        Field name1 = getDeclaredField(User.class, "name");
        setFieldValue(name1, user, "hdu");
        setDeclaredFieldValue(User.class, "name", user, "liYun");

        System.out.println(user);
        System.out.println(beanUtils);


    }


    public static Constructor<?> getDeclaredConstructor(Class<?> clazz, Class<?>... parameterTypes) {

        Constructor<?> declaredConstructor = null;
        try {
            declaredConstructor = clazz.getDeclaredConstructor(parameterTypes);
            declaredConstructor.setAccessible(true);
            return declaredConstructor;

        } catch (Exception e) {

            log.error("BeanUtils getDeclaredConstructor error", e);
        }

        return null;
    }

    public static <T> T newInstance(Constructor<T> constructor, Object... initArgs) {

        try {
            return (T) constructor.newInstance(initArgs);

        } catch (Exception e) {
            log.error("BeanUtils newInstance error", e);
        }
        return null;
    }



    // ReflectionFactory 生成新的无参构造函数 [为反序列化生成的构造器]
    private static Constructor<?> getConstructorForSerialization(Class<?> clazz) {

        Constructor<?> constructor = cacheConstructor.get(clazz.getName());

        if (constructor != null) {
            return constructor;
        }

        ReflectionFactory reflectionFactory = ReflectionFactory.getReflectionFactory();
        constructor = reflectionFactory.newConstructorForSerialization(clazz, objectConstructor);
        constructor.setAccessible(true);
        cacheConstructor.putIfAbsent(clazz.getName(), constructor);

        return constructor;
    }

    @SuppressWarnings({"unchecked"})
    public static <T> T newInstanceUseSerialization(Class<T> clazz) {

        try {
            Constructor<?> constructorForSerialization = getConstructorForSerialization(clazz);
            return (T) constructorForSerialization.newInstance();

        } catch (Exception e) {
            log.error(e.getMessage());
        }
        return null;
    }


    /**
     * @param clazz - 要被查找的class
     * @param name  - 唯一的名称的方法， 此方法不能有重写，如果方法有重写，则结果不确定
     * @return 查到到的Method
     */
    public static Method findUniqueMethod(Class<?> clazz, String name) {

        Method[] declaredMethods = clazz.getDeclaredMethods();
        Method method = getMethod(declaredMethods, name);
        method.setAccessible(true);
        return method;
    }

    //ReflectionUtils   查找到祖类 getSuperClass
    @SneakyThrows
    public static Method getDeclaredMethod(Class<?> clazz, String name, Class<?>... parameterTypes) {
        Method method = clazz.getDeclaredMethod(name, parameterTypes);
        method.setAccessible(true);
        return method;
    }

    private static Method getMethod(Method[] declaredMethods, String name) {

        Method method = null;
        for (Method declaredMethod : declaredMethods) {

            if (declaredMethod == null) {
                continue;
            }

            if (name.equals(declaredMethod.getName())) {
                method = declaredMethod;
                break;
            }
        }
        return method;
    }

    public static Method getDeclaredMethod(Object obj, String name, Class<?>... parameterTypes) {
        Method method = null;
        try {

            Class<?> clazz = obj.getClass();
            if (parameterTypes == null || parameterTypes.length == 0) {
                Method[] declaredMethods = clazz.getDeclaredMethods();

                //返回值不同的重写 getDeclaredMethods会获取父类方法  但实际调用还是多态
                method = getMethod(declaredMethods, name);
            } else {
                method = obj.getClass().getDeclaredMethod(name, parameterTypes);
            }

            method.setAccessible(true);
        } catch (Exception e) {
            log.error("BeanUtils getDeclaredMethod error", e);
        }
        return method;
    }

    //
    public static Object invokeNonOverloadMethod(Object obj, String methodName, Object... args) {

        Method method = getDeclaredMethod(obj, methodName);

        try {
            return method.invoke(obj, args);
        } catch (Exception e) {
            log.error("BeanUtils invokeNonOverloadMethod error", e);
        }

        return null;
    }

    public static Object invokeMethod(Method method, Object target, Object... args) {
        try {
            return method.invoke(target, args);
        } catch (Exception e) {
            log.error("e=", e);
        }
        return null;
    }


    public static Object invokeStaticMethod(Method method, Object... args) {
        return invokeMethod(method, null, args);
    }


    public static Field getDeclaredField(Class<?> clazz, String name) {

        try {
            Field field = clazz.getDeclaredField(name);
            field.setAccessible(true);
            return field;
        } catch (Exception e) {
            log.error("BeanUtils invokeMethod error", e);
        }
        return null;
    }

    public static Object getDeclaredFieldValue(Class<?> clazz, String name, Object obj) {

        try {
            Field field = clazz.getDeclaredField(name);
            field.setAccessible(true);
            return field.get(obj);
        } catch (Exception e) {
            log.error("BeanUtils getDeclaredFieldValue error", e);
        }
        return null;
    }

    public static void setFieldValue(Field field, Object obj, Object value) {

        try {
            field.set(obj, value);
        } catch (Exception e) {
            log.error("BeanUtils setFieldValue error", e);
        }
    }

    public static void setDeclaredFieldValue(Class<?> clazz, String name, Object obj, Object value) {

        try {
            Field field = clazz.getDeclaredField(name);
            field.setAccessible(true);
            field.set(obj, value);
        } catch (Exception e) {
            log.error("BeanUtils invokeMethod error", e);
        }
    }


    @FunctionalInterface
    public interface ExceptionableRunnable {
        public void run() throws Exception;
    }

    public static void execRunnable(ExceptionableRunnable runnable) {
        try {
            runnable.run();
        } catch (Exception e) {
            log.error("BeanUtils execRunnable error", e);
        }
    }
}
